package com.hugy.test.util;


import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.hugy.entity.BaseEntity;
import com.hugy.test.entity.es.*;
import io.searchbox.action.Action;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.*;
import io.searchbox.indices.IndicesExists;
import io.searchbox.indices.aliases.AddAliasMapping;
import io.searchbox.indices.aliases.GetAliases;
import io.searchbox.indices.aliases.ModifyAliases;
import io.searchbox.indices.mapping.GetMapping;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.data.elasticsearch.annotations.Document;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

@Slf4j
public class JestUtils {

    // 从 application 中获取 jestClient
    private static JestClient jestClient = SpringContextHolder.getBean("jestClient");

    /**
     * 统一执行入口
     *
     * @param action
     * @param <T>
     * @return
     */
    private static <T extends JestResult> T execute(Action<T> action) {
        T result;
        try {
            result = jestClient.execute(action);
        } catch (IOException e) {
            throw new RuntimeException(StrUtil.format("JestUtils.execute =>>  exception : {}", e.getMessage()));
        }
        if (null == result || !result.isSucceeded()) {
            throw new RuntimeException(null == result ? "JestUtils.execute error" : result.getErrorMessage());
        }
        return result;
    }

    /**
     * 查询文档
     */

    public static <Q extends QueryParam, E extends BaseEntity> PageResult<E> getDocumentTest(Q param, Class<E> clazz) {
        String indexName = getAlias(clazz);
        PageResult<E> pagerResult = PageResult.empty();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 用哪种query，以及查询的字段和值
        searchSourceBuilder.query(getQueryBuilder(param));
        int pageSize = param.getPageSize();
        int pageNo = param.getPageNo();
        searchSourceBuilder.from(param.getPageNo());
        searchSourceBuilder.size(param.getPageSize());
        Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(indexName).build();
        SearchResult result = execute(search);
        List<SearchResult.Hit<E, Void>> hits = result.getHits(clazz);
        List<E> list = Lists.newArrayList();
        if (CollectionUtil.isNotEmpty(hits)) {
            hits.forEach(h -> {
                E source = h.source;
                source.setId(h.id);
                list.add(source);
            });
            int total = result.getTotal().intValue();
            pagerResult.setTotal(total);
            if (param.isPaging() && pageSize > 0) {
                pagerResult.setTotalPages(total % pageSize == 0 ? total / pageSize : total / pageSize + 1);
            }
        }
        pagerResult.setData(list);
        pagerResult.setPageNo(pageNo);
        pagerResult.setPageSize(pageSize);
        return pagerResult;
    }

    /**
     * 给索引添加别名
     */

    public <E extends BaseEntity>  JestResult  createAliasTest(Class<E> clazz) {
        List<String> indics = new ArrayList<>();
        indics.add("posts");
        indics.add("postsss");
        AddAliasMapping addAliasMapping = new AddAliasMapping.Builder(indics, "mypost").build();
        return execute(new ModifyAliases.Builder(addAliasMapping).build());
    }


    /**
     * 根据index获取别名
     */
    public static <E extends BaseEntity> String getIndexAlias(Class<E> clazz) {
        String index = getAlias(clazz);
        return execute(new GetAliases.Builder().addIndex(index).build()).getJsonString();
    }

    /**
     * 判断index是否存在
     */
    public static <E extends BaseEntity> JestResult indicesExists(Class<E> clazz){
        String index = getAlias(clazz);
        IndicesExists indicesExists=new IndicesExists.Builder(index).build();
        return execute(indicesExists);
    }

    /**
     * count
     * 必须要指定精确的index
     */
    public static <E extends BaseEntity> JestResult countTest(Class<E> clazz){
        Count count = new Count.Builder()
                .addIndex("cvf_2018-10-09")
                .addType("cloud_logs")
                .build();
        return execute(count);
    }

    public static <E extends BaseEntity> JestResult getIndexList(Class<E> clazz){
        GetMapping getMapping = new GetMapping.Builder().build();
        return execute(getMapping);
    }

    /**
     * 获取alias别名
     *
     * @param clazz
     * @param <E>
     * @return
     */
    private static <E extends BaseEntity> String getAlias(Class<E> clazz) {
        Document annotation = AnnotationUtil.getAnnotation(clazz, Document.class);
        String alias = annotation.type();
        if (StrUtil.isEmpty(alias)) {
            return getIndex(clazz);
        }
        return alias;
    }

    /**
     * 获取indexName
     *
     * @param clazz
     * @param <E>
     * @return
     */
    private static <E extends BaseEntity> String getIndex(Class<E> clazz) {
        Document annotation = AnnotationUtil.getAnnotation(clazz, Document.class);
        String indexName = annotation.indexName();
        if (StrUtil.isEmpty(indexName)) {
            throw new RuntimeException("请实体类上面配置indexName");
        }
        return indexName;
    }

    /**
     * BoolQueryBuilder进行复合查询
     *
     * @param param
     * @return
     */
    private static BoolQueryBuilder getQueryBuilder(QueryParam param) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        List<Term> terms = param.getTerms();
        if (CollectionUtil.isEmpty(terms)) {
            return boolQueryBuilder;
        }
        TermEnum type = terms.get(0).getType();
        for (Term term : terms) {
            if (CollectionUtil.isEmpty(term.getTerms())) {
                buildQuery(boolQueryBuilder, term.getColumn(), type, term.getTermType(), term.getValue());
            } else {
                buildChildQueryBuilder(boolQueryBuilder, term, type);
            }
        }
        return boolQueryBuilder;
    }

    /**
     * 转换term
     *
     * @param boolQueryBuilder
     * @param term
     * @return
     */
    private static QueryBuilder buildChildQueryBuilder(BoolQueryBuilder boolQueryBuilder, Term term, TermEnum parentType) {
        List<Term> terms = term.getTerms();
        TermEnum type = terms.get(0).getType();
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        buildQuery(queryBuilder, term.getColumn(), type, term.getTermType(), term.getValue());
        terms.forEach(t -> buildQuery(queryBuilder, t.getColumn(), type, t.getTermType(), t.getValue()));
        if (TermEnum.OR == parentType) {
            boolQueryBuilder.should(queryBuilder);
        } else {
            boolQueryBuilder.must(queryBuilder);
        }
        return boolQueryBuilder;
    }

    /**
     * 获取条件
     *
     * @param column
     * @param termType
     * @param value
     * @return
     */
    private static QueryBuilder buildQuery(BoolQueryBuilder boolQueryBuilder, String column, TermEnum type, TermTypeEnum termType, Object value) {
        if ("id".equals(column)) {
            column = "_id";
        }
        QueryBuilder queryBuilder;
        switch (termType) {
            case GTE:
                queryBuilder = QueryBuilders.rangeQuery(column).gte(value);
                break;
            case GT:
                queryBuilder = QueryBuilders.rangeQuery(column).gt(value);
                break;
            case LTE:
                queryBuilder = QueryBuilders.rangeQuery(column).lte(value);
                break;
            case LT:
                queryBuilder = QueryBuilders.rangeQuery(column).lt(value);
                break;
            case BETWEEN:
                ArrayList list1 = toList(value);
                queryBuilder = QueryBuilders.rangeQuery(column).from(list1.get(0)).to(list1.get(1));
                break;
            case NBETWEEN:
                ArrayList list2 = toList(value);
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.rangeQuery(column).from(list2.get(0)).to(list2.get(1)));
                break;
            case IN:
                queryBuilder = QueryBuilders.termsQuery(column, toList(value));
                break;
            case NIN:
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery(column, toList(value)));
                break;
            case LIKE:
                queryBuilder = QueryBuilders.wildcardQuery(column, "*" + value + "*");
                break;
            case LLIKE:
                queryBuilder = QueryBuilders.wildcardQuery(column, "*" + value);
                break;
            case RLIKE:
                queryBuilder = QueryBuilders.wildcardQuery(column, value + "*");
                break;
            case NLIKE:
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.wildcardQuery(column, "*" + value + "*"));
                break;
            case EQ:
                if (value instanceof Collection) {
                    queryBuilder = QueryBuilders.termsQuery(column, CollectionUtil.newArrayList((Collection) value));
                } else {
                    queryBuilder = QueryBuilders.termsQuery(column, value);
                }
                break;
            case NE:
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery(column, value));
                break;
            case NULL:
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(column));
                break;
            case NNULL:
                queryBuilder = QueryBuilders.existsQuery(column);
                break;
            case EMPTY:
                queryBuilder = QueryBuilders.termsQuery(column, "");
                break;
            case NEMPTY:
                queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery(column, ""));
                break;
            case QUERY_STRING:
                queryBuilder = QueryBuilders.commonTermsQuery(column, value);
                break;
            case MATCH:
                queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery(column, value));
                break;
            case MATCH_PHRASE:
                queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchPhraseQuery(column, value));
                break;
            default:
                throw new RuntimeException(String.format("operator[%s] not supported in elasticsearch TermType.", column));
        }
        if (TermEnum.OR == type) {
            boolQueryBuilder.should(queryBuilder);
        } else {
            boolQueryBuilder.must(queryBuilder);
        }
        return boolQueryBuilder;
    }

    /**
     * 把object 转化为list
     *
     * @param object
     * @return
     */
    private static ArrayList toList(Object object) {
        ArrayList list;
        if (object instanceof Collection) {
            list = CollectionUtil.newArrayList((Collection) object);
        } else if (object instanceof Arrays) {
            list = CollectionUtil.newArrayList((Arrays) object);
        } else {
            Object[] split = StrUtil.split(object.toString(), ",");
            list = toList(split);
        }
        return list;
    }
}
